﻿using System;
using System.Collections.Generic;
using System.IO;

namespace Framework.Utils
{
    public class CircularBuffer: Stream
    {
	    public int ChunkSize { get; private set; }=8192;//1KB

	    private readonly Queue<byte[]> chunks = new Queue<byte[]>();
        private readonly Queue<byte[]> caches = new Queue<byte[]>();

        public int WriteEnd { get; private set; }

        public int ReadBegin { get; private set; }
		
        private byte[] chunk;

	    public CircularBuffer()
	    {
		    this.Encrease();
	    }
	    public CircularBuffer(int chunkSize)
	    {
		    this.ChunkSize = chunkSize;
		    this.Encrease();
	    }

        public override long Length
        {
            get
            {
	            if (this.chunks.Count == 0)
		            return 0;
	            return (this.chunks.Count - 1) * ChunkSize + this.WriteEnd - this.ReadBegin;
            }
        }

        public void Encrease()
        {
            byte[] buffer;
            if (this.caches.Count > 0)
            {
                buffer = this.caches.Dequeue();
            }
            else
            {
                buffer = new byte[ChunkSize];
            }
            this.chunks.Enqueue(buffer);
            this.chunk = buffer;
        }
        public void Decrease()
        {
	        if (this.caches.Count > 1)
	        {
		        int rm = this.caches.Count / 2;
		        while (this.caches.Count > rm)
		        {
			        this.caches.Dequeue();
		        }
	        }
        }

        void Store(byte[] chunk)
        {
            this.caches.Enqueue(chunk);
        }

        public byte[] First
        {
            get
            {
                if (this.chunks.Count == 0)
                {
                    this.Encrease();
                }
                return this.chunks.Peek();
            }
        }

        public byte[] WriteableChunk
        {
            get
            {
	            MakeSureWriteable();
                return this.chunk;
            }
        }
        ///确保可写入
        void MakeSureWriteable()
        {
	        if (this.chunks.Count == 0)
	        {
		        this.Encrease();
		        WriteEnd = 0;
	        }
	        if (WriteEnd == ChunkSize)
	        {
		        this.Encrease();
		        WriteEnd = 0;
	        }
        }


	    // 从CircularBuffer读到stream
	    public void Read(Stream stream, int count)
	    {
		    if (count > this.Length)
		    {
			    throw new Exception($"bufferList length < count, {Length} {count}");
		    }

		    int size = 0;
		    while (size < count)
		    {
			    int n = count - size;
			    if (ChunkSize - this.ReadBegin > n)
			    {
				    stream.Write(this.First, this.ReadBegin, n);
				    this.ReadBegin += n;
				    size += n;
			    }
			    else
			    {
				    stream.Write(this.First, this.ReadBegin, ChunkSize - this.ReadBegin);
				    size += ChunkSize - this.ReadBegin;
				    this.ReadBegin = 0;
				    this.Store(chunks.Dequeue());
			    }
		    }
	    }
	    
	    /// 从stream写入CircularBuffer
	    public void Write(Stream stream)
		{
			int count = (int)(stream.Length - stream.Position);
			
			int size = 0;
			while (size < count)
			{
				MakeSureWriteable();
				int n = count - size;
				if (ChunkSize - this.WriteEnd > n)
				{
					stream.Read(this.chunk, this.WriteEnd, n);
					this.WriteEnd += count - size;
					size += n;
				}
				else
				{
					stream.Read(this.chunk, this.WriteEnd, ChunkSize - this.WriteEnd);
					size += ChunkSize - this.WriteEnd;
					this.WriteEnd = ChunkSize;
				}
			}
		}
	    

	    /// 把CircularBuffer中数据写入buffer
        public override int Read(byte[] buffer, int offset, int count)
        {
	        if (buffer.Length < offset + count)
	        {
		        throw new Exception($"bufferList length < coutn, buffer length: {buffer.Length} {offset} {count}");
	        }

	        long length = this.Length;
			if (length < count)
            {
	            count = (int)length;
            }

            int size = 0;
            while (size < count)
            {
                int n = count - size;
				if (ChunkSize - this.ReadBegin > n)
                {
                    Array.Copy(this.First, this.ReadBegin, buffer, size + offset, n);
                    this.ReadBegin += n;
                    size += n;
                }
                else
                {
                    Array.Copy(this.First, this.ReadBegin, buffer, size + offset, ChunkSize - this.ReadBegin);
                    size += ChunkSize - this.ReadBegin;
                    this.ReadBegin = 0;
                    this.Store(chunks.Dequeue());
                }
            }

	        return count;
        }

	    /// 把buffer写入CircularBuffer中
        public override void Write(byte[] buffer, int offset, int count)
        {
	        int size = 0;
            while (size < count)
            {
	            MakeSureWriteable();
	            int n = count - size;
                if (ChunkSize - this.WriteEnd > n)
                {
                    Array.Copy(buffer, size + offset, this.chunk, this.WriteEnd, n);
                    this.WriteEnd += count - size;
                    size += n;
                }
                else
                {
                    Array.Copy(buffer, size + offset, this.chunk, this.WriteEnd, ChunkSize - this.WriteEnd);
                    size += ChunkSize - this.WriteEnd;
                    this.WriteEnd = ChunkSize;
                }
            }
        }

	    public override void Flush()
	    {
		    throw new NotImplementedException();
		}

	    public override long Seek(long offset, SeekOrigin origin)
	    {
		    ReadBegin += (int)offset;
		    if (this.ReadBegin == this.ChunkSize)
		    {
			    ReadBegin = 0;
			    Store(chunks.Dequeue());
		    }
		    return ReadBegin;
	    }

	    public override void SetLength(long value)
	    {
		    throw new NotImplementedException();
		}

	    public override bool CanRead
	    {
		    get
		    {
			    return true;
		    }
	    }

	    public override bool CanSeek
	    {
		    get
		    {
			    return false;
		    }
	    }

	    public override bool CanWrite
	    {
		    get
		    {
			    return true;
		    }
	    }

	    public override long Position { get; set; }
    }
}